﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Net;
using System.Security.Principal;
using System.IO;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Security;
using System.Net.NetworkInformation;

namespace Com.CodePlex.SPC3.SPbAM {

	public class Automation {

		public static bool IsPowerShell20InstalledOnThisMachine() {
			try {
				using (PowerShell ps = PowerShell.Create()) {
					ps.AddScript("get-host");
					ps.AddCommand("out-string");
					var results = ps.Invoke();
					foreach (var item in results) {
						if (item.ToString().Contains("2.0")) {
							return true;
						}
					}
				}
			} catch (Exception e) {
				int i = 0;
			}
			return false;
		}

        public static AutomationResult RunPowerShellScript(string server, string script, IList<CommandParameterWithType> paras) {

            //todo: update UI with progress

            #region find if localhost
            var match = Regex.Match(server, @"\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b");
            if (!match.Success) {
                IPAddress[] ips = Dns.GetHostAddresses(server);
                server = ips[0].ToString(); //todo: should i handle the error
            }
            bool localhost = false;
            IPHostEntry localhostentry = Dns.GetHostEntry(Dns.GetHostName());
            foreach (IPAddress ip in localhostentry.AddressList) {
                if (ip.AddressFamily == AddressFamily.InterNetwork) {
                    if (ip.ToString() == server) {
                        localhost = true;
                    }
                }
            }
            if (server == "127.0.0.1") {
                localhost = true;
            }
            #endregion

            List<string> errors = new List<string>();
            List<string> results = new List<string>();
            if (localhost) {
                using (PowerShell ps = PowerShell.Create()) {
                    ps.AddCommand(script, true);
                    paras.ToList().ForEach(i => {
                        ps.AddParameter(i.Name, i.Value);
                    });
                    ps.AddParameter("runat", "server");
                    try {
                        var results1 = ps.Invoke<string>();
                        foreach (var item in results1) {
                            results.AddRange(item.ToString().Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries));
                        }
                        foreach (var item in ps.Streams.Error) {
                            errors.AddRange(item.ToString().Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries));
                        }
                    } catch (PSSecurityException sex) {
                        errors.Add(sex.ToString());
                    }
                }
            } else {
                string username = paras.ToList().Single(i => i.Name == "username").Value.ToString();
                string password = paras.ToList().Single(i => i.Name == "password").Value.ToString();

                NetworkCredential creds1 = new NetworkCredential(username, password.ToString());
                using (new NetworkConnection(@"\\" + server + @"\c$\psinstall", creds1)) {
                    Directory.CreateDirectory(@"\\" + server + @"\c$\psinstall\spbam\");
                    File.Copy(script, @"\\" + server + @"\c$\psinstall\spbam\" + new FileInfo(script).Name, true);
                }

                //$creds = New-Object System.Management.Automation.PSCredential $username, (ConvertTo-SecureString $password -AsPlainText -force) 
                //$result = Invoke-Command -ComputerName $computername -Credential $creds -Authentication CredSSP -scriptBlock {
                Char[] input = password.ToCharArray();
                SecureString spassword = new SecureString();
                for (int i = 0; i < input.Length; i++) {
                    spassword.AppendChar(input[i]);
                }
                spassword.MakeReadOnly();
                //IntPtr pBStr = Marshal.SecureStringToBSTR(SecurePassword);
                //Marshal.FreeBSTR(pBStr);
                PSCredential creds = new PSCredential(username, spassword);
                WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, server, 5985, "/wsman", "http://schemas.microsoft.com/powershell/Microsoft.PowerShell", creds);
                connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Credssp;
                Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo);
                runspace.Open();

                //RunspaceInvoke invoker = new RunspaceInvoke(runspace);
                //invoker.Invoke("Set-ExecutionPolicy Unrestricted");

                Pipeline pipeline = runspace.CreatePipeline();
                //pipeline.Commands.AddScript(System.IO.File.ReadAllText(script), true); //todo: load script files or modules, or could copy file to server (allows libraries then)
                Command cmd = new Command(@"c:\psinstall\spbam\" + new FileInfo(script).Name, true);
                paras.ToList().ForEach(i => {
                    cmd.Parameters.Add(i.Name, i.Value);
                });
                cmd.Parameters.Add("runat", "remote");
                pipeline.Commands.Add(cmd);
                pipeline.Commands.Add("out-string");
                var results2 = pipeline.Invoke();
                foreach (var item in results2) {
                    results.AddRange(item.ToString().Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries));
                }
                if (pipeline.Error != null && pipeline.Error.Count > 0) {
                    while (!pipeline.Error.EndOfPipeline) {
                        System.Collections.ObjectModel.Collection<ErrorRecord> items = (System.Collections.ObjectModel.Collection<ErrorRecord>)pipeline.Error.Read();
                        foreach (var item in items) {
                            errors.AddRange(item.ToString().Split(new char[2] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries));
                        }
                    }
                }
            }

            return new AutomationResult() {
                Results = results,
                Errors = errors
            };
        }

			//http://nightroman.wordpress.com/2008/10/16/get-names-of-script-parameters/
		public static IList<CommandParameterWithType> GetScriptParameters(string path) { //todo: change to use (Get-Command).Parameters
			IList<CommandParameterWithType> paras = new List<CommandParameterWithType>();
			string[] lines = File.ReadAllLines(path);
			bool inparamblock = false;
			for (int i = 0; i < lines.Length; i++) {
				if (lines[i].Contains("param")) {
					inparamblock = true;
				} else if (inparamblock) {
					if (lines[i].StartsWith(")")) {
						break;
					}
					Match match = Regex.Match(lines[i], "\\s*\\[(\\w+)\\]\\$(\\w+)(\\s*=\\s*\"(.*)\",*)*"); //"\t[string]$vmstorepath = \"D:\\vms\","
					if (match.Success) {
						paras.Add(new CommandParameterWithType() {
							Type = match.Groups[1].Value,
							Name = match.Groups[2].Value,
							Value = match.Groups[4].Value,
						});
					}
				}
			}
			return paras;
		}
	}

    public class CommandParameterWithType {
        public bool IsReadOnly = false;
        public string Type;
        public string Name;
        public object Value;
    }

    public class AutomationResult {
        public List<string> Results;
        public List<string> Errors;
    }
}